package com.cloud.common.rpc;

import com.alibaba.csp.sentinel.context.ContextUtil;
import com.cloud.common.bean.ResultInfo;
import com.cloud.common.bean.SessionInfo;
import com.cloud.common.context.*;
import com.cloud.common.util.JsonUtils;
import io.netty.channel.ChannelHandler.Sharable;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.seata.core.context.RootContext;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * 服务端处理
 *
 * @author fengwenjin
 */
@Slf4j
@Sharable
@Component
public class ServerHandler extends SimpleChannelInboundHandler<RPCMessage> {

    @Autowired
    private RequestMappingManager mappingManager;

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, RPCMessage msg) throws Exception {
        RPCMessage resMsg = new RPCMessage();
        resMsg.setSequence(msg.getSequence());

        Request request = JsonUtils.parseObject(msg.getBodyData(), Request.class);
        RequestMapping requestMapping = mappingManager.getRequestMapping(request.getRequestUri());
        if (requestMapping == null) {
            resMsg.setBodyData(JsonUtils.toJSONString(ResultInfo.badRequest()));
            ctx.channel().writeAndFlush(resMsg);
            return;
        }

        try {
            ContextUtil.enter("sentinel_rpc_context");
            Object result = invoke(request, requestMapping);
            resMsg.setBodyData(JsonUtils.toJSONString(result));
        } catch (Throwable e) {
            log.error(e.getMessage(), e);
            ResultInfo resultInfo = new ResultInfo();
            resultInfo.setCode(HttpStatus.INTERNAL_SERVER_ERROR.value());
            resMsg.setBodyData(JsonUtils.toJSONString(resultInfo));
        } finally {
            ctx.channel().writeAndFlush(resMsg);
            ContextUtil.exit();
        }
    }

    private Object invoke(Request request, RequestMapping requestMapping) throws Exception {
        //设置方法参数
        LinkedHashMap<String, Class> parameterMap = requestMapping.getParameterMap();
        Object[] params = new Object[parameterMap.size()];
        if (params.length > 0) {
            int index = 0;
            Map<String, Object> requestParam = JsonUtils.parseObject(request.getRequestParam(), Map.class);
            Iterator<String> itor = parameterMap.keySet().iterator();
            while (itor.hasNext()) {
                String name = itor.next();
                Object value = requestParam.get(name);
                if (value == null || value instanceof String) {
                    params[index] = value;
                } else if (value instanceof Map) {
                    params[index] = JsonUtils.parseObject(JsonUtils.toJSONString(value), parameterMap.get(name));
                } else {
                    params[index] = JsonUtils.parseObject(value.toString(), parameterMap.get(name));
                }
                index++;
            }
        }

        // 设置上下文
        if (StringUtils.isNotEmpty(request.getSessionInfo())) {
            SessionContext.set(JsonUtils.parseObject(request.getSessionInfo(), SessionInfo.class));
        }
        if (StringUtils.isNotEmpty(request.getVersionInfo())) {
            VersionContext.set(request.getVersionInfo());
        }
        if (StringUtils.isNotEmpty(request.getXid())) {
            RootContext.bind(request.getXid());
        }
        TraceContext.set(request.getTraceId());

        try {
            //反射调用方法
            Object target = requestMapping.getHandlerTarget();
            Method method = requestMapping.getHandlerMethod().getMethod();
            return method.invoke(target, params);
        } finally {
            TraceContext.remove();
            SessionContext.remove();
            VersionContext.remove();
        }
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        ctx.close();
    }

}
